﻿using System;
using System.ComponentModel;

using Outcoder.ComponentModel;
using Outcoder.Tests.Mocks;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Outcoder.Tests.ComponentModel
{
    /// <summary>
    ///This is a test class for PropertyChangedNotifierTest and is intended
    ///to contain all PropertyChangedNotifierTest Unit Tests
    ///</summary>
	[TestClass]
	public class PropertyChangedNotifierTest
	{
		/// <summary>
		///A test for NotifyChanged
		///</summary>
		[TestMethod]
		public void OnPropertyChangedShouldBeRaisedWhenAPropertyChanges()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();
			string propertyName = null;
			string oldValue = null;
			string newValue = null;

			mockNotifyPropertyChanged.PropertyChanged 
				+= ((sender, e) =>
                      	{
                      		propertyName = e.PropertyName;
                      		var notifierEventArgs = e as PropertyChangedEventArgs<string>;
							if (notifierEventArgs != null)
							{
								oldValue = notifierEventArgs.OldValue;
								newValue = notifierEventArgs.NewValue;
							}
                      	});
            
			var testValue1 = mockNotifyPropertyChanged.TestPropertyString = "Test";

			Assert.AreEqual("TestPropertyString", propertyName);
			Assert.AreEqual(null, oldValue);
			Assert.AreEqual(testValue1, newValue);

			var testValue2 = mockNotifyPropertyChanged.TestPropertyString = "Test2";
			Assert.AreEqual("TestPropertyString", propertyName);
			Assert.AreEqual(testValue1, oldValue);
			Assert.AreEqual(testValue2, newValue);
		}

		/// <summary>
		///A test for NotifyChanged
		///</summary>
		[TestMethod]
		public void OnPropertyChangedShouldBeRaisedWhenAPropertyChangesWithLamda()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();
			string propertyName = null;
			string oldValue = null;
			string newValue = null;

			mockNotifyPropertyChanged.PropertyChanged 
				+= ((sender, e) =>
                  	{
                  		propertyName = e.PropertyName;
						var notifierEventArgs = e as PropertyChangedEventArgs<string>;
						if (notifierEventArgs != null)
						{
							oldValue = notifierEventArgs.OldValue;
							newValue = notifierEventArgs.NewValue;
						}
                  	});

			mockNotifyPropertyChanged.TestPropertyLambda = "Test";
			Assert.AreEqual("TestPropertyLambda", propertyName);

			propertyName = null;
			mockNotifyPropertyChanged.TestPropertyLambda = "Test2";
			Assert.AreEqual("TestPropertyLambda", propertyName);
		}

		/// <summary>
		///A test for NotifyChanged
		///</summary>
		[TestMethod]
		public void NotifierShouldAssignProperty()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();
			string propertyName = null;
			int oldValue = 0;
			int newValue = 0;

			mockNotifyPropertyChanged.PropertyChanged
				+= ((sender, e) =>
					{
						propertyName = e.PropertyName;
						var notifierEventArgs = e as PropertyChangedEventArgs<int>;
						if (notifierEventArgs != null)
						{
							oldValue = notifierEventArgs.OldValue;
							newValue = notifierEventArgs.NewValue;
						}
					});

			int changingOldValue = 0;
			int changingNewValue = 0;

			mockNotifyPropertyChanged.PropertyChanging 
				+= ((sender, e) =>
			    	{
			    		var notifierEventArgs = (PropertyChangingEventArgs<int>)e;
			    		changingOldValue = notifierEventArgs.OldValue;
			    		changingNewValue = notifierEventArgs.NewValue;
			    	});

			var testValue1 = mockNotifyPropertyChanged.TestPropertyAssigned = 2;

			/* Firstly check changing event. */
			Assert.AreEqual(0, changingOldValue);
			Assert.AreEqual(newValue, changingNewValue);
			/* Now check changed event. */
			Assert.AreEqual("TestPropertyAssigned", propertyName);
			Assert.AreEqual(0, oldValue);
			Assert.AreEqual(testValue1, newValue);

			var testValue2 = mockNotifyPropertyChanged.TestPropertyAssigned = 3;
			Assert.AreEqual("TestPropertyAssigned", propertyName);
			Assert.AreEqual(testValue1, oldValue);
			Assert.AreEqual(testValue2, newValue);
		}

		/// <summary>
		///A test for NotifyChanged
		///</summary>
		[TestMethod]
		public void NotifierShouldNotAssignPropertyIfCancelled()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();

			mockNotifyPropertyChanged.PropertyChanging
				+= ((sender, e) =>
				{
					var notifierEventArgs = (PropertyChangingEventArgs<string>)e;
					notifierEventArgs.Cancel();
				});

			mockNotifyPropertyChanged.TestPropertyAssigned2 = "Test";
			Assert.IsNull(mockNotifyPropertyChanged.TestPropertyAssigned2);
		}

    	/// <summary>
		///A test for NotifyChanged
		///</summary>
		[TestMethod]
		public void NotifierShouldNotRaiseEventIfPropertyIsTheSameAsNewValue()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();
			bool notificationOccured = false;
			var testValue1 = mockNotifyPropertyChanged.TestPropertyAssigned = 2;

			mockNotifyPropertyChanged.PropertyChanged
				+= ((sender, e) =>
				{
					notificationOccured = true;
				});

			mockNotifyPropertyChanged.TestPropertyAssigned = testValue1;
			Assert.IsFalse(notificationOccured, "Notification should not have occured because value is the same.");
		}

		[TestMethod]
		public void NotifierShouldAssignPropertyWithLambda()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged();
			string propertyName = null;
			string oldValue = null;
			string newValue = null;

			mockNotifyPropertyChanged.PropertyChanged
				+= ((sender, e) =>
				{
					propertyName = e.PropertyName;
					var notifierEventArgs = e as PropertyChangedEventArgs<string>;
					if (notifierEventArgs != null)
					{
						oldValue = notifierEventArgs.OldValue;
						newValue = notifierEventArgs.NewValue;
					}
				});

			var testValue1 = mockNotifyPropertyChanged.TestPropertyAssignedLambda = "Test";
			Assert.AreEqual("TestPropertyAssignedLambda", propertyName);
			Assert.AreEqual(null, oldValue);
			Assert.AreEqual(testValue1, newValue);

			var testValue2 = mockNotifyPropertyChanged.TestPropertyAssignedLambda = "Test2";
			Assert.AreEqual("TestPropertyAssignedLambda", propertyName);
			Assert.AreEqual(testValue1, oldValue);
			Assert.AreEqual(testValue2, newValue);
		}
        
		[TestMethod]
		public void NotifierShouldNotPreventGarbageCollectionOfOwner()
		{
			var notifier = CreateNotifier();
			GC.Collect();
			var owner = notifier.Owner;
			
			Assert.IsNull(owner, "Owner should have been collected.");
		}

    	PropertyChangeNotifier CreateNotifier()
    	{
			var result = new MockNotifyPropertyChanged();
			
			result.PropertyChanged += result_PropertyChanged;

    		result.TestPropertyString = "Test";
			Assert.IsTrue(notificationSignalled, "Property changed was not raised.");

    		return result.PropertyChangeNotifier;
		}

		bool notificationSignalled;

		void result_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			notificationSignalled = true;
		}

		[TestMethod]
		public void NotifierShouldCacheArgs()
		{
			var mockNotifyPropertyChanged = new MockNotifyPropertyChanged(false);

			PropertyChangedEventArgs changedEventArgs = null;
			PropertyChangingEventArgs changingEventArgs = null;

			mockNotifyPropertyChanged.PropertyChanged
				+= ((sender, e) =>
				{
					changedEventArgs = e;
				});

			mockNotifyPropertyChanged.PropertyChanging
				+= ((sender, e) =>
				{
					changingEventArgs = e;
				});

			mockNotifyPropertyChanged.TestPropertyAssignedLambda = "Test";
			Assert.IsNotNull(changedEventArgs);
			Assert.IsNotNull(changingEventArgs);
			var firstChangedArgs = changedEventArgs;
			var firstChangingArgs = changingEventArgs;
			mockNotifyPropertyChanged.TestPropertyAssignedLambda = "Test2";
			Assert.AreSame(firstChangedArgs, changedEventArgs);
			Assert.AreSame(firstChangingArgs, changingEventArgs);

			mockNotifyPropertyChanged.TestPropertyAssigned = 2;
			Assert.IsNotNull(changedEventArgs);
			Assert.IsNotNull(changingEventArgs);
			firstChangedArgs = changedEventArgs;
			firstChangingArgs = changingEventArgs;
			mockNotifyPropertyChanged.TestPropertyAssigned = 3;
			Assert.AreSame(firstChangedArgs, changedEventArgs);
			Assert.AreSame(firstChangingArgs, changingEventArgs);
		}
	}
}
